package com.lzq.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lzq.train.business.domain.*;
import com.lzq.train.business.dto.ConfirmOrderMQDto;
import com.lzq.train.business.enums.ConfirmOrderStatusEnum;
import com.lzq.train.business.enums.RedisKeyPreEnum;
import com.lzq.train.business.enums.SeatColEnum;
import com.lzq.train.business.enums.SeatTypeEnum;
import com.lzq.train.business.mapper.ConfirmOrderMapper;
import com.lzq.train.business.req.ConfirmOrderDoReq;
import com.lzq.train.business.req.ConfirmOrderQueryReq;
import com.lzq.train.business.req.ConfirmOrderTicketReq;
import com.lzq.train.business.resp.ConfirmOrderQueryResp;
import com.lzq.train.common.exception.BusinessException;
import com.lzq.train.common.exception.BusinessExceptionEnum;
import com.lzq.train.common.resp.PageResp;
import com.lzq.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class ConfirmOrderService {

    private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);

    @Resource
    private ConfirmOrderMapper confirmOrderMapper;

    @Resource
    private DailyTrainTicketService dailyTrainTicketService;

    @Resource
    private DailyTrainCarriageService dailyTrainCarriageService;

    @Resource
    private DailyTrainSeatService dailyTrainSeatService;

    @Resource
    private AfterConfirmOrderService afterConfirmOrderService;

    @Resource
    private Redisson redisson;

    @Resource
    private SkTokenService skTokenService;

    public void save(ConfirmOrderDoReq req) {
        DateTime now = DateTime.now();
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
        if (ObjectUtil.isNull(confirmOrder.getId())) {
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.insert(confirmOrder);
        } else {
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.updateByPrimaryKey(confirmOrder);
        }
    }

    public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        confirmOrderExample.setOrderByClause("id desc");
        ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);

        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);

        PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id) {
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    /**
     * 确认购票操作
     * 多线程环境下的线程安全
     * 第一版：使用jdk锁，缺点仅仅支持单机
     * 第二版：使用redis分布式锁
     *        问题：1. 会带来锁超时的问题，如获取锁后，代码执行超过5s怎么办，那么锁就失效了，依然有安全问题
     *             2. 线程在5s 内执行完了 锁依然存在，阻塞其他线程
     *        解决方法：
     *              使用守护线程，也就是看门狗方案，监控锁的过期时间，如下面是5s , 那么如果3s时代码还没执行完，那么刷新锁时间为5s,一直重复
     *              当主线程执行完了，守护线程随之销毁。（自己实现复杂，使用Redisson工具）
     * 第三版：使用Redisson 看门狗解决锁超时问题
     *
     * // @SentinelResource(value = "doConfirm"): 定义限流资源 还需要配置限流规则
     */
    // @SentinelResource(value = "doConfirm")
    @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    public void doConfirm(ConfirmOrderMQDto dto) {
        // 第三版 redis
        //String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(req.getDate()) + "-" + req.getTrainCode(); // 锁：锁的是日期加车次
        // setIfAbsent() 就是setnx
//        Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 5, TimeUnit.SECONDS);// 锁过期时间5s
//        if (Boolean.TRUE.equals(setIfAbsent)) {
//            // 获得锁
//            LOG.info("恭喜获得锁：lockKey->{}", lockKey);
//        } else {
//            // 获取锁失败 并不知道票抢完没有 提示稍后再试
//            LOG.info("很遗憾获取锁失败：lockKey->{}", lockKey);
//            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);
//        }

        String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(dto.getDate()) + "-" + dto.getTrainCode(); // 锁：锁的是日期加车次
        RLock lock = null;
        try {
            // 使用redisson 自带看门狗方案
            lock = redisson.getLock(lockKey);
            /**
             * 参数：
             *     waitTime：等待时间，如果获取不到锁，最多等待多久
             *     leaseTime：锁过期时间
             *     unit：时间单位
             */
            // boolean tryLock = lock.tryLock(30,10, TimeUnit.SECONDS); // 不带看门狗  获取不到锁 等待30s
            boolean tryLock = lock.tryLock(0, TimeUnit.SECONDS);// 带看门狗 默认过期时间30s 自动刷新成30s循环
            if (tryLock) {
                // 获得锁
                LOG.info("恭喜获得锁：lockKey->{}", lockKey);
                // 继续执行
            } else {
                // 获取锁失败 并不知道票抢完没有 提示稍后再试
                //LOG.info("很遗憾获取锁失败：lockKey->{}", lockKey);
                //throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);

                LOG.info("没抢到锁，有其他线程正在出票，不做任何处理");
                return;
            }
            // 省略业务数据校验，如：车次是否存在，余票是否存在，车次是否在有效期内，tickets条数>0，同乘客同车次是否已买过

            // 获取锁之后 一个线程执行排队购票逻辑
            while (true) {
                // 取出确认订单表的记录 状态为init 分页处理订单 每次取N条，循环执行，可以降低内存消耗
                ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
                confirmOrderExample.setOrderByClause("id asc");
                ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
                criteria.andDateEqualTo(dto.getDate())
                        .andTrainCodeEqualTo(dto.getTrainCode())
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1, 5); // 分页处理订单
                List<ConfirmOrder> list = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);

                if (list.isEmpty()) {
                    LOG.info("没有要处理的订单，结束循环");
                    break;
                } else {
                    LOG.info("有{}个订单，开始处理", list.size());
                }

                // 一条一条的卖 如果当前车站票不足，则更新状态为无票，继续处理下一个订单
                list.forEach(confirmOrder -> {
                    try {
                        sell(confirmOrder);
                    } catch (BusinessException e) {
                        if (e.getE().equals(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR)) {
                            LOG.info("余票不足，继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateStatus(confirmOrder);
                        } else {
                            throw e;
                        }
                    }
                });
            }
        } catch (InterruptedException e) {
            LOG.info("购票异常", e);
        } finally {
            LOG.info("购票流程结束释放锁！");
            // 释放锁 释放当前线程的锁
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 更新状态
     * @param confirmOrder
     */
    public void updateStatus(ConfirmOrder confirmOrder) {
        ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
        confirmOrderForUpdate.setId(confirmOrder.getId());
        confirmOrderForUpdate.setUpdateTime(new Date());
        confirmOrderForUpdate.setStatus(confirmOrder.getStatus());
        confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
    }

    /**
     * 售卖
     * @param confirmOrder
     */
    public void sell(ConfirmOrder confirmOrder) {
        // 构造ConfirmOrderDoReq
        ConfirmOrderDoReq req = new ConfirmOrderDoReq();
        req.setDate(confirmOrder.getDate());
        req.setTrainCode(confirmOrder.getTrainCode());
        req.setMemberId(confirmOrder.getMemberId());
        req.setDailyTrainTicketId(confirmOrder.getDailyTrainTicketId());
        req.setTickets(JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class));
        req.setStart(confirmOrder.getStart());
        req.setEnd(confirmOrder.getEnd());
        req.setLogId("");
        req.setImageCodeToken("");
        req.setImageCode("");

        // 更新订单为处理中 避免重复处理
        LOG.info("更新订单状态为处理中,避免重复处理，confirm_order.id：{}", confirmOrder.getId());
        confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
        updateStatus(confirmOrder);

        Date date = req.getDate();
        String trainCode = req.getTrainCode();
        String start = req.getStart();
        String end = req.getEnd();
        List<ConfirmOrderTicketReq> tickets = req.getTickets();
        // 查出余票记录，需要得到真实的库存
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
        LOG.info("查出余票记录：{}", dailyTrainTicket);
        // 扣减余票数量，并判断余票是否足够  与扣减库存而不是直接扣减数据库
        reduceTickets(req, dailyTrainTicket);

        // 最终选座结果
        List<DailyTrainSeat> finalSeatList = new ArrayList<>();

        // 偏移量目的：一次循环就可以支持选座。
        // 开始选座前，判断当前是否选座，选座则要计算相对第一个座位的偏移值。否则不用计算偏移量
        // 比如选择的是C1,D2，则偏移值是：[0,5]
        // A1 C1 - D1 F1
        // A2 C2 - D2 F2
        // 比如选择的是A1,B1,C1，则偏移值是：[0,1,2]
        // A1 B1 C1 - D1 F1
        // A2 B2 C2 - D2 F2
        ConfirmOrderTicketReq ticketReq = tickets.get(0);
        if (StrUtil.isNotBlank(ticketReq.getSeat())) { // 判断前端选座参数是否为空
            LOG.info("本次购票有选座");
            // 查出本次选座的座位类型都有哪些列，用于计算所选座位与第一个座位的偏离值
            List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(ticketReq.getSeatTypeCode());
            LOG.info("本次选座的座位类型包含的列：{}", colEnumList);

            // 组成和前端两排选座一样的列表，用于作参照的座位列表，例：referSeatList = {A1, C1, D1, F1, A2, C2, D2, F2}
            List<String> referSeatList = new ArrayList<>();
            for (int i = 1; i <= 2; i++) {
                for (SeatColEnum seatColEnum : colEnumList) {
                    referSeatList.add(seatColEnum.getCode() + i);
                }
            }
            LOG.info("用于作参照的两排座位：{}", referSeatList);

            // ------ 计算相对偏移值 -------------
            List<Integer> offsetList = new ArrayList<>();
            // 绝对偏移值，即：在参照座位列表中的位置
            List<Integer> absoluteOffsetList = new ArrayList<>();
            for (ConfirmOrderTicketReq orderTicketReq : tickets) {
                int index = referSeatList.indexOf(orderTicketReq.getSeat());
                absoluteOffsetList.add(index);
            }
            LOG.info("计算得到所有座位的绝对偏移值：{}", absoluteOffsetList);
            for (Integer index : absoluteOffsetList) {
                int offset = index - absoluteOffsetList.get(0);
                offsetList.add(offset);
            }
            LOG.info("计算得到所有座位的相对第一个座位的偏移值：{}", offsetList);

            // 开始选座
            getSeat(
                    finalSeatList,
                    date,
                    trainCode,
                    ticketReq.getSeatTypeCode(),
                    ticketReq.getSeat().split("")[0], // 从A1得到A
                    offsetList,
                    dailyTrainTicket.getStartIndex(),
                    dailyTrainTicket.getEndIndex()
            );

        } else {
            LOG.info("本次购票无选座");
            // 循环每一张票 自动选座
            for (ConfirmOrderTicketReq confirmOrderTicketReq : tickets) {
                getSeat(
                        finalSeatList,
                        date,
                        trainCode,
                        confirmOrderTicketReq.getSeatTypeCode(),
                        null,
                        null,
                        dailyTrainTicket.getStartIndex(),
                        dailyTrainTicket.getEndIndex()
                );
            }
        }
        LOG.info("最终选座：{}", finalSeatList);
        // 选中座位后事务处理：
        // 座位表修改售卖情况sell
        // 余票详情表修改余票
        // 为会员增加购票记录
        // 更新确认订单为成功
        try {
            afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, tickets, confirmOrder);
        } catch (Exception e) {
            LOG.error("保存购票信息失败", e);
            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_EXCEPTION);
        }
    }

    /**
     * 计算某座位在区间内是否可卖
     * 例：sell=10001，本次购买区间站1~4，则区间已售000
     * 全部是0，表示这个区间可买；只要有1，就表示区间内已售过票
     * <p>
     * 选中后，要计算购票后的sell，比如原来是10001，本次购买区间站1~4
     * 方案：构造本次购票造成的售卖信息01110，和原sell 10001按位与，最终得到11111
     */
    private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) {

        // 模拟耗时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 00001, 00000
        String sell = dailyTrainSeat.getSell();
        //  000, 000
        String sellPart = sell.substring(startIndex, endIndex);
        if (Integer.parseInt(sellPart) > 0) {
            LOG.info("座位{}在本次车站区间{}~{}已售过票，不可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            return false;
        } else {
            LOG.info("座位{}在本次车站区间{}~{}未售过票，可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            //  111,   111
            String curSell = sellPart.replace('0', '1');
            // 0111,  0111
            curSell = StrUtil.fillBefore(curSell, '0', endIndex);
            // 01110, 01110
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());

            // 当前区间售票信息curSell 01110与库里的已售信息sell 00001按位与，即可得到该座位卖出此票后的售票详情
            // 15(01111), 14(01110 = 01110|00000)
            int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            //  1111,  1110
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            // 01111, 01110
            newSell = StrUtil.fillBefore(newSell, '0', sell.length());
            LOG.info("座位{}被选中，原售票信息：{}，车站区间：{}~{}，即：{}，最终售票信息：{}"
                    , dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, curSell, newSell);
            dailyTrainSeat.setSell(newSell);
            return true;
        }
    }

    /**
     * 挑座位
     */
    private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String column, List<Integer> offsetList, Integer startIndex, Integer endIndex) {
        List<DailyTrainSeat> getSeatList = new ArrayList<>(); // 临时变量保存选座座位
        List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
        LOG.info("共查出{}个符合条件的车厢", carriageList.size());

        // 一个车箱一个车箱的获取座位数据
        for (DailyTrainCarriage dailyTrainCarriage : carriageList) {
            LOG.info("开始从车厢{}选座", dailyTrainCarriage.getIndex());
            getSeatList = new ArrayList<>(); // 换车厢也要清空
            List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByCarriage(date, trainCode, dailyTrainCarriage.getIndex());
            LOG.info("车厢{}的座位数：{}", dailyTrainCarriage.getIndex(), seatList.size());
            for (DailyTrainSeat dailyTrainSeat : seatList) {
                // 判断column,有值的话对比列号
                String col = dailyTrainSeat.getCol();
                Integer seatIndex = dailyTrainSeat.getCarriageSeatIndex();
                // 判断当前座位不能被选中
                boolean alreadyChooseFlag = false;
                for (DailyTrainSeat finalSeat : finalSeatList) {
                    if (finalSeat.getId().equals(dailyTrainSeat.getId())) {
                        alreadyChooseFlag = true;
                        break;
                    }
                }
                if (alreadyChooseFlag) {
                    LOG.info("座位{}已被选中，继续判断下一个座位", seatIndex);
                    continue;
                }

                if (StrUtil.isBlank(column)) {
                    LOG.info("无选座");
                } else {
                    if (!column.equals(col)) {
                        LOG.info("座位{}列值不对，继续判断下一个座位，当前列值：{}，目标列值：{}", seatIndex, col, column);
                        continue;
                    }
                }
                // 判断座位是否可选
                boolean isChoose = calSell(dailyTrainSeat, startIndex, endIndex);
                if (isChoose) {
                    // 座位可选
                    LOG.info("选中座位");
                    getSeatList.add(dailyTrainSeat);
                } else {
                    continue;
                }

                // 根据offset选剩下的座位
                boolean isGetAllOffsetSeat = true;
                if (CollUtil.isNotEmpty(offsetList)) {
                    LOG.info("有偏移值：{}，校验偏移的座位是否可选", offsetList);
                    // 从索引1开始，索引0就是当前已选中的票
                    for (int j = 1; j < offsetList.size(); j++) {
                        Integer offset = offsetList.get(j);
                        // 座位在库的索引是从1开始
                        int nextIndex = seatIndex + offset - 1;

                        // 有选座时，一定是在同一个车箱
                        if (nextIndex >= seatList.size()) {
                            LOG.info("座位{}不可选，偏移后的索引超出了这个车箱的座位数", nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }

                        DailyTrainSeat nextDailyTrainSeat = seatList.get(nextIndex);
                        boolean isChooseNext = calSell(nextDailyTrainSeat, seatIndex, endIndex);
                        if (isChooseNext) {
                            LOG.info("座位{}被选中", nextDailyTrainSeat.getCarriageSeatIndex());
                            getSeatList.add(nextDailyTrainSeat);
                        } else {
                            LOG.info("座位{}不可选", nextDailyTrainSeat.getCarriageSeatIndex());
                            isGetAllOffsetSeat = false;
                            break;
                        }
                    }
                }
                if (!isGetAllOffsetSeat) {
                    getSeatList = new ArrayList<>(); // 清空
                    continue; // 跳出当前循环
                }
                // 保存选好的座位
                finalSeatList.addAll(getSeatList);
                return;
            }
        }
    }

    /**
     * 预扣减
     *
     * @param req
     * @param dailyTrainTicket
     */
    private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
        for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
            String seatTypeCode = ticketReq.getSeatTypeCode();
            SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seatTypeEnum) {
                case YDZ -> {
                    int countLeft = dailyTrainTicket.getYdz() - 1;
                    if (countLeft < 0) {
                        // 余票不足
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setYdz(countLeft);
                }
                case EDZ -> {
                    int countLeft = dailyTrainTicket.getEdz() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setEdz(countLeft);
                }
                case RW -> {
                    int countLeft = dailyTrainTicket.getRw() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setRw(countLeft);
                }
                case YW -> {
                    int countLeft = dailyTrainTicket.getYw() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setYw(countLeft);
                }
            }
        }
    }

    /**
     * 降级处理方法，需包含限流方法的所有参数和BlockException参数
     * @param req
     * @param e
     */
    public void doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
        LOG.info("购票被限流：{}", req);
        // 抛出限流异常
        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
    }

    /**
     * 查询前面有几个人在排队
     * @param id
     * @return
     */
    public Integer queryLineCount(Long id) {
        ConfirmOrder confirmOrderDb = confirmOrderMapper.selectByPrimaryKey(id);
        if (confirmOrderDb == null) {
            LOG.info("订单不存在，id：{}", id);
            return -999; // 无该笔订单
        }
        // 获取当前订单状态
        ConfirmOrderStatusEnum orderStatusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrderDb.getStatus());
        // 构造返回值
        int result = switch(orderStatusEnum) {
            case PENDING -> 0; // 处理中，返回0
            case SUCCESS -> -1; // 成功，返回-1
            case FAILURE -> -2; // 失败，返回-2
            case EMPTY -> -3; // 无票，返回-3
            case CANCEL -> -4; // 取消，返回-4
            case INIT -> 999; // 当前订单还没开始处理，需要查到真实排队数量
        };

        if (result == 999) {
            // 查出前排队数量
            // 查询条件（初始 && 创建时间小于当前订单） or (处理中 && 创建时间小于当前订单)
            ConfirmOrderExample example = new ConfirmOrderExample();
            example.or()
                    .andDateEqualTo(confirmOrderDb.getDate())
                    .andTrainCodeEqualTo(confirmOrderDb.getTrainCode())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode())
                    .andCreateTimeLessThan(confirmOrderDb.getCreateTime());
            example.or()
                    .andDateEqualTo(confirmOrderDb.getDate())
                    .andTrainCodeEqualTo(confirmOrderDb.getTrainCode())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode())
                    .andCreateTimeLessThan(confirmOrderDb.getCreateTime());

            return Math.toIntExact(confirmOrderMapper.countByExample(example)); // 排队位置
        }

        return result;
    }

    public Integer cancel(Long id) {
        ConfirmOrderExample example = new ConfirmOrderExample();
        ConfirmOrderExample.Criteria criteria = example.createCriteria();
        criteria.andIdEqualTo(id)
                .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());

        ConfirmOrder confirmOrder = new ConfirmOrder();
        confirmOrder.setStatus(ConfirmOrderStatusEnum.CANCEL.getCode());
        return confirmOrderMapper.updateByExampleSelective(confirmOrder, example);
    }
}
